﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Data;
using System.Threading.Tasks;
using System.Collections;
using FTI.Core.Threading;

namespace FTI.Core
{
    public class CSVFileReader
        : StreamReader
    {
        public DataTable Table { get; set; }

        public bool EnableConcurrency { get; set; }

        public char Delimiter { get; set; }

        public string Errors { get; set; }

        public CSVFileReader(Stream stream, Encoding encoding)
            : base(stream, encoding)
        {
            InitializeReader();
        }

        public CSVFileReader(string path, Encoding encoding)
            : base(path, encoding)
        {
            InitializeReader();
        }

        protected virtual void InitializeReader()
        {
            Delimiter = ',';

            // Hit the Thread Pool Singleton to ensure that it has been spooled up.
            if(ThreadPool.Instance == null)
            {
                
            }
        }

        public bool Read(ref DataTable table)
        {
            if (!BaseStream.CanRead)
            {
                throw new FileLoadException(Resources.FileLoadException);
            }

            if(table == null)
            {
                throw new ArgumentNullException("table");
            }

            Table = table;

            int
                count = 0;

            StringBuilder oErrors = new StringBuilder();

            while (!EndOfStream)
            {   // We will continue to read lines until we have read the entire stream
                try
                {
                    string oLine = ReadLine();

                    if (oLine.IsNotNullOrEmpty())
                    {
                        count++; // increment line count

                        if (EnableConcurrency)
                        {
                            ThreadPool.Instance.QueueTask(() =>
                            {
                                Table.Rows.Add(MapLineToDataRow(oLine, count));
                            }, null);
                        }
                        else
                        {
                            Table.Rows.Add(MapLineToDataRow(oLine, count));
                        }
                    }
                }
                catch (InvalidDataException pInvalidData)
                {
                    oErrors.AppendLine(pInvalidData.Message);
                }
                catch(Exception pUnknown)
                {
                    oErrors
                        .AppendFormat("{0}  at line: {1}", pUnknown.Message, count)
                        .AppendLine();
                }
            }

            ThreadPool.Wait(); // waite until the thread pool is finished working on the lines

            // expose any errors that may have occured while reading in the data
            Errors = oErrors.ToString();

            return Errors.Length == 0 && table.Rows.Count == count;
        }

        public object[] MapLineToDataRow(string line, int count)
        {
            ArrayList oLineData = new ArrayList();

            string
                value = String.Empty;

            int
                pos = 0,
                idx = 0,
                start = -1;

            while(pos < line.Length)
            {
                if(line[pos] == '"')
                {   // qouted strings are processed with special characters found in them
                    pos++; // we need to skip the qoute

                    start = pos; // we are at the beginning of the value

                    while(pos < line.Length)
                    {
                        if ((pos + 1) >= line.Length || line[pos + 1] == '"')
                        {   // we are looking for the next qoute
                            pos++;
                            if ((pos + 1) >= line.Length ||
                                line[pos + 1] == Delimiter)
                            {
                                break;
                            }
                        }
                        pos++;
                    }
                    value = (pos == line.Length
                        ? line.Substring(start, (pos - 1) - start)
                        : line.Substring(start, pos - start)).Replace("\"\"", "\"");
                }
                else
                {   // unqouted value
                    start = pos; // we are at the beginning of the value

                    while (pos < line.Length && line[pos] != Delimiter)
                    {
                        pos++;
                    }
                    value = line.Substring(start, pos - start);
                }

                if (idx < Table.Columns.Count)
                {   // as long as the column index is less than the number of columns found in the data schema
                    if (value.IsNotNullOrEmpty())
                    {
                        oLineData.Add(Convert.ChangeType(value.Trim(), Table.Columns[idx].DataType));
                    }
                    else if (!Table.Columns[idx].AllowDBNull)
                    {
                        if (Table.Columns[idx].DataType.GetConstructor(new Type[] { }) != null || !Table.Columns[idx].DataType.IsClass)
                        {
                            oLineData.Add(Activator.CreateInstance(Table.Columns[idx].DataType));
                        }
                        else
                        {
                            throw new InvalidDataException(String.Format(Resources.InvalidDataExceptionNull, Table.Columns[idx].ColumnName, count));
                        }
                    }
                    else
                    {
                        oLineData.Add(DBNull.Value);
                    }
                }
                else
                {   // the csv being imported does not match the expected data schema
                    throw new InvalidDataException(String.Format(Resources.InvalidDataException, count));
                }
                // increment the column index
                idx++;

                //  move position to the next column start position
                while (pos < line.Length && line[pos] != Delimiter)
                {
                    pos++;
                }

                if(pos < line.Length)
                {   // this will move us past the delimiter
                    pos++;
                }
            }

            if (line[pos - 1] == Delimiter)
            {   // a delimiter was detected at the end of the line this should signify that the last column was empty
                if (idx < Table.Columns.Count)
                {   // as long as the column index is less than the number of columns found in the data schema
                    if (Table.Columns[idx].DataType.GetConstructor(new Type[] { }) != null || !Table.Columns[idx].DataType.IsClass)
                    {
                        oLineData.Add(Activator.CreateInstance(Table.Columns[idx].DataType));
                    }
                    else if (!Table.Columns[idx].AllowDBNull)
                    {
                        if (Table.Columns[idx].DataType.GetConstructor(new Type[] { }) != null || !Table.Columns[idx].DataType.IsClass)
                        {
                            oLineData.Add(Activator.CreateInstance(Table.Columns[idx].DataType));
                        }
                        else
                        {
                            throw new InvalidDataException(String.Format(Resources.InvalidDataExceptionNull, Table.Columns[idx].ColumnName, count));
                        }
                    }
                    else
                    {
                        oLineData.Add(DBNull.Value);
                    }
                }
                else
                {   // the csv being imported does not match the expected data schema
                    throw new InvalidDataException(String.Format(Resources.InvalidDataException, count));
                }
                idx++;
            }

            if (idx != Table.Columns.Count)
            {   // the csv being imported does not match the expected data schema
                throw new InvalidDataException(String.Format(Resources.InvalidDataException, count));
            }

            return oLineData.ToArray();
        }


        
    }
}
